/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-7
  File:    text_serializer.cpp  
  Description: 

*/

#include "stdafx.h"

#define TOK_SIZE L"$__Size"
#define TOK_DATA L"$__Data"

using namespace m;
using namespace m::io;
using namespace m::error;

//0 A     17 R     34 i     51 z
//1 B     18 S     35 j     52 0
//2 C     19 T     36 k     53 1
//3 D     20 U     37 l     54 2
//4 E     21 V     38 m     55 3
//5 F     22 W     39 n     56 4
//6 G     23 X     40 o     57 5
//7 H     24 Y     41 p     58 6
//8 I     25 Z     42 q     59 7
//9 J     26 a     43 r     60 8
//10 K    27 b     44 s     61 9
//11 L    28 c     45 t     62 +
//12 M    29 d     46 u     63 /
//13 N    30 e     47 v
//14 O    31 f     48 w
//15 P    32 g     49 x
//16 Q    33 h     50 y

static const wchar_t Base64Table[] = 
{
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
	'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
	'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
	'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '='
};

#define BASE64_PAD Base64Table[ARRAYSIZE(Base64Table) - 1]

namespace m
{
	namespace text
	{
		static wchar_t* Base64Encode(const byte* Data, size_t size);
		static byte* Base64Decode(const wchar_t* Data, size_t* decodedSize);

		text_serializer::text_serializer()
			: _Document(nullptr),
			_Objects(nullptr),
			_Last(nullptr)
		{
			assign_new(_Document, new json_document());
		}
		
		text_serializer::~text_serializer()
		{
			clear();
		}

		void text_serializer::write_object_start(const wchar_t * name)
		{
			if (ValueFor(name) != nullptr)
			{
				throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_DUP_NAME), "Cannot write object, value exists.");
			}

			ref_storage track1;
			json_node* newNode = create(track1, new json_node());
			newNode->set_object();
			if (name)
			{
				newNode->set_name(name);
			}
			InsertValue(newNode);
			PushObject(newNode);
			m::clear(_Last);
		}

		void text_serializer::InsertValue(json_node* node)
		{
			if (CurObject())
			{
				if (!_Last && CurObject()->child())
				{
					assign(_Last, CurObject()->child());
					while (_Last->sibling())
					{
						assign(_Last, _Last->sibling());
					}
				}

				if (_Last)
				{
					node->insert_after(_Last);
				}
				else
				{
					CurObject()->insert_child(node);
				}
				assign(_Last, node);
			}
			else
			{
				assert(!_Document->root());
				if (!_Document->root())
				{
					_Document->set_root(node);
				}
			}
		}
		
		void text_serializer::write_object_end()
		{
			PopObject();
			m::clear(_Last);
		}

		void text_serializer::write_value(const wchar_t * name, const wchar_t * string)
		{
			if (!CurObject())
			{
				throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot write string, not currently writing an object.");
			}

			ref_storage track1;
			json_node* newNode = create(track1, new json_node());
			if (string)
			{
				newNode->set_value(string);
			}
			else
			{
				newNode->set_nil();
			}
			newNode->set_name(name);
			InsertValue(newNode);
		}
		
		void text_serializer::write_value(const wchar_t * name, double num)
		{
			if (!CurObject())
			{
				throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot write number, not currently writing an object.");
			}

			ref_storage track1;
			json_node* newNode = create(track1, new json_node());
			newNode->set_value(num);
			newNode->set_name(name);
			InsertValue(newNode);
		}

		void text_serializer::write_value(const wchar_t * name, int8_t num)
		{
			write_value(name, (int32_t)num);
		}

		void text_serializer::write_value(const wchar_t * name, int16_t num)
		{
			write_value(name, (int32_t)num);
		}
		
		void text_serializer::write_value(const wchar_t * name, int32_t num)
		{
			if (!CurObject())
			{
				throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot write int, not currently writing an object.");
			}

			ref_storage track1;
			json_node* newNode = create(track1, new json_node());
			StringCchPrintf(_Buf, ARRAYSIZE(_Buf), L"%d", num);
			newNode->set_value(_Buf);
			newNode->set_name(name);
			InsertValue(newNode);
		}
		
		void text_serializer::write_value(const wchar_t * name, int64_t num)
		{
			if (!CurObject())
			{
				throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot write long, not currently writing an object.");
			}

			ref_storage track1;
			json_node* newNode = create(track1, new json_node());
			StringCchPrintf(_Buf, ARRAYSIZE(_Buf), L"%lld", num);
			newNode->set_value(_Buf);
			newNode->set_name(name);
			InsertValue(newNode);
		}
		
		void text_serializer::write_value(const wchar_t * name, bool bit)
		{
			if (!CurObject())
			{
				throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot write boolean, not currently writing an object.");
			}

			ref_storage track1;
			json_node* newNode = create(track1, new json_node());
			newNode->set_value(bit);
			newNode->set_name(name);
			InsertValue(newNode);
		}

		void text_serializer::write_value(const wchar_t * name, const byte * data, size_t sz)
		{
			write_object_start(name);
			if ((int64_t)sz > INT64_MAX)
			{
				throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_BUFFER_OVERFLOW), "Size too large.");
			}
			write_value(TOK_SIZE, (int64_t)sz);
			wchar_t* base64 = Base64Encode(data, sz);
			write_value(TOK_DATA, base64);
			free(base64);
			write_object_end();
		}

		bool text_serializer::read_object_start(const wchar_t * name, size_t* countChildren)
		{
			if (countChildren)
			{
				*countChildren = 0;
			}

			ref_storage track1;
			json_node* cur = nullptr;

			if (CurObject())
			{
				cur = ValueFor(name);
			}
			else
			{
				// In the case that we're reading the document for the first time, set the current object to the document root and return it.
				cur = _Document->root();
			}
			if (cur)
			{
				if (!cur->is_object() && !cur->is_nil())
				{
					throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot read object, value not an object.");
				}

				if (cur->is_object())
				{
					if (countChildren)
					{
						json_node* child = cur->child();
						while (child)
						{
							++*countChildren;
							child = child->sibling();
						}
					}

					PushObject(cur);
					return true;
				}
			}
			return false;
		}

		void text_serializer::read_object_end()
		{
			if (!CurObject())
			{
				throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot read object end, value not an object.");
			}
			
			PopObject();
		}

		void text_serializer::read_value(const wchar_t * name, wchar_t ** string)
		{
			assert(name && string);
			if (name && string)
			{
				m::clear(*string);

				json_node* cur = ValueFor(name);
				if (cur)
				{
					if (cur->type() != JsonString && cur->type() != JsonNil)
					{
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot read string, value not an string.");
					}
					if (cur->as_string())
					{
						assign(*string, cur->as_string());
					}
				}
			}
		}

		void text_serializer::read_value(const wchar_t * name, double * num)
		{
			assert(name && num);
			if (name && num)
			{
				*num = 0.;
				json_node* cur = ValueFor(name);
				if (cur)
				{
					if (cur->type() != JsonNumber)
					{
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot read number, value not an string.");
					}

					*num = cur->as_number();
				}
			}
		}

		void text_serializer::read_value(const wchar_t * name, int8_t * num)
		{
			assert(num);
			if (num)
			{
				int32_t tNum;
				read_value(name, &tNum);
				*num = (int8_t)tNum;
			}
		}

		void text_serializer::read_value(const wchar_t * name, int16_t * num)
		{
			assert(num);
			if (num)
			{
				int32_t tNum;
				read_value(name, &tNum);
				*num = (int16_t)tNum;
			}
		}

		void text_serializer::read_value(const wchar_t * name, int32_t * num)
		{
			assert(name && num);
			if (name && num)
			{
				*num = 0;
				json_node* cur = ValueFor(name);
				if (cur)
				{
					if (cur->type() != JsonString)
					{
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot read int, value not an string.");
					}

					if (swscanf_s(cur->as_string(), L"%d", num) < 1)
					{
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot read int, invalid format.");
					}
				}
			}
		}

		void text_serializer::read_value(const wchar_t * name, int64_t * num)
		{
			assert(name && num);
			if (name && num)
			{
				*num = 0;
				json_node* cur = ValueFor(name);
				if (cur)
				{
					if (cur->type() != JsonString)
					{
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot read long, value not an string.");
					}

					if (swscanf_s(cur->as_string(), L"%lld", num) < 1)
					{
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot read long, invalid format.");
					}
				}
			}
		}

		void text_serializer::read_value(const wchar_t * name, bool * bit)
		{
			assert(name && bit);
			if (name && bit)
			{
				*bit = false;
				json_node* cur = ValueFor(name);
				if (cur)
				{
					if (cur->type() != JsonBool)
					{
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot read bool, value not an string.");
					}

					*bit = cur->as_bool();
				}
			}
		}

		void text_serializer::read_value(const wchar_t * name, byte ** data, size_t * sz)
		{
			assert(name && data && sz);
			if (name && data && sz)
			{
				*sz = 0;
				if (*data)
				{
					delete[] *data;
					*data = nullptr;
				}
				std::exception_ptr exptr;
				if (read_object_start(name, nullptr))
				{
					try
					{
						int64_t sz64;
						read_value(TOK_SIZE, &sz64);
						if (sz64 > SIZE_MAX)
						{
							throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_BUFFER_OVERFLOW), "Size too large.");
						}
						*sz = (size_t)sz64;

						json_node* cur = ValueFor(TOK_DATA);
						if (cur)
						{
							if (cur->type() != JsonString && cur->type() != JsonNil)
							{
								throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot read binary, value not an string.");
							}

							if (cur->type() == JsonString)
							{
								size_t decodedSize;
								*data = Base64Decode(cur->as_string(), &decodedSize);

								if (decodedSize != (size_t)*sz)
								{
									throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_INVALID_OPERATION), "Cannot read binary, data corrupt.");
								}
							}
						}
					}
					catch (...)
					{
						exptr = std::current_exception();
					}
					read_object_end();
					if (exptr)
					{
						std::rethrow_exception(exptr);
					}
				}
			}
		}

		void text_serializer::clear()
		{
			while (_Objects)
			{
				PopObject();
			}
			m::clear(_Last);
			assign_new(_Document, new json_document());
		}

		void text_serializer::load(text_buffer* buffer)
		{
			assert(buffer);
			if (buffer)
			{
				clear();
				_Document->load_document(buffer);
			}
		}

		void text_serializer::save(text_buffer* buffer)
		{
			assert(buffer);
			if (buffer)
			{
				_Document->save_document(buffer);
				clear();
			}
		}

		json_node *text_serializer::ValueFor(const wchar_t * name)
		{
			if (CurObject())
			{
				json_node* cur = CurObject()->child();
				json_node* ret = nullptr;
				while (cur)
				{
					if (_wcsicmp(cur->name(), name) == 0)
					{
						if (ret)
						{
							throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_DUP_NAME), "A serialization value in this document is duplicated.");
						}
						ret = cur;
					}

					cur = cur->sibling();
				}
				return ret;
			}
			return nullptr;
		}

		text_serializer::ObjectState::ObjectState()
			: Object(nullptr),
			Last(nullptr)
		{
		}

		text_serializer::ObjectState::~ObjectState()
		{
			m::clear(Object);
		}

		void * text_serializer::ObjectState::operator new(size_t sz)
		{
			return ::operator new(sz, m::alloc::pool);
		}

		void text_serializer::ObjectState::operator delete(void * ptr)
		{
			return ::operator delete(ptr, m::alloc::pool);
		}

		void text_serializer::PushObject(json_node* node)
		{
			assert(node);
			if (node)
			{
				ObjectState* newState = new ObjectState();
				assign(newState->Object, node);
				newState->Last = _Objects;
				_Objects = newState;
			}
		}

		void text_serializer::PopObject()
		{
			if (_Objects)
			{
				ObjectState* toDelete = _Objects;
				_Objects = _Objects->Last;
				delete toDelete;
			}
		}

		json_node* text_serializer::CurObject()
		{
			if (_Objects)
			{
				return _Objects->Object;
			}
			return nullptr;
		}

		wchar_t* Base64Encode(const byte* Data, size_t size)
		{
			size_t retLen = size * 4 / 3, padLen = retLen;
			if (retLen % 4)
			{
				retLen -= (retLen % 4);
				retLen += 4;
			}
			++retLen;

			assert(retLen >= 4);
			if (retLen >= 4)
			{
				wchar_t* ret = new wchar_t[retLen];
				size_t retStart = 0;

				for (size_t i = 0; i < size; i += 3)
				{
					uint32_t accumulator = 0;

					for (size_t j = i; j < i + 3; ++j)
					{
						if (j < size)
						{
							accumulator |= Data[j];
						}
						accumulator <<= 8;
					}

					for (size_t k = retStart; k < retStart + 4; ++k)
					{
						if (k <= padLen)
						{
							uint32_t data = (accumulator & 0xFC000000) >> 26;
							accumulator <<= 6;
							ret[k] = Base64Table[data];
						}
						else
						{
							ret[k] = BASE64_PAD;
						}
					}
					retStart += 4;
				}

				ret[retLen - 1] = 0;
				return ret;
			}

			return nullptr;
		}

		byte* Base64Decode(const wchar_t* Data, size_t* decodedSize)
		{
			size_t size = wcslen(Data);
			size_t bufLen = size * 3 / 4;
			if (bufLen % 3)
			{
				bufLen -= bufLen % 3;
				bufLen += 3;
			}

			byte* ret = new byte[bufLen];
			size_t bufIdx = 0;

			*decodedSize = 0;

			for (size_t i = 0; i < size; i += 3)
			{
				uint32_t accumulator = 0;
				size_t read = 0;

				for (size_t j = 0; j < 4; ++j)
				{
					accumulator <<= 6;

					if (bufIdx < size && Data[bufIdx] != BASE64_PAD)
					{
						uint32_t data;
						if (Data[bufIdx] >= 'A' && Data[bufIdx] <= 'Z')
						{
							data = Data[bufIdx] - 'A';
						}
						else if (Data[bufIdx] >= 'a' && Data[j] <= 'z')
						{
							data = Data[bufIdx] - 'a' + 26;
						}
						else if (Data[bufIdx] >= '0' && Data[j] <= '9')
						{
							data = Data[bufIdx] - '0' + 52;
						}
						else if (Data[bufIdx] == '+')
						{
							data = 63;
						}
						else if (Data[bufIdx] == '/')
						{
							data = 64;
						}
						else
						{
							break;
						}

						++bufIdx;
						accumulator |= data;
					}
				}

				// were assuming the output buffer size is padded such that it is a multiple of 3 and that any extra output will be ignored by the caller.
				for (size_t k = i; k < i + 3 && k < bufIdx * 3 / 4; ++k)
				{
					uint32_t value = (accumulator & 0xFF0000) >> 16;
					accumulator <<= 8;
					ret[k] = value;
					++*decodedSize;
				}
			}

			return ret;
		}
	}
}